#!/usr/bin/python
"""
This script generates the fastest possible C code for the following
function: given a string, see if it belongs to a known set of strings.
If it does, return an enum corresponding to that string.
It can be thought of as internalizing a string.

TODO: this is not actually the fastest possible implementation.
Assuming our set of known strings is "a", "abba2", "bo", the fastest
possible implementation would be along the lines:

int FindTag(char *s, size_t len)
{
    char c = *s++; --len;
    if (c == 'a') {
        if (0 == len) return Tag_A;
        if ((len == 4) && memeq(s, "bba2", 4)) return Tag_Abba2;
    } else if (c == 'b') {
        if ((len == 1) && (*s == 'o')) return Tag_Bo;
    }
    return Tag_NotFound;
}

or:

uint32_t GetUpToFour(char*& s, size_t& len)
{
    CrashIf(0 == len);
    size_t n = 0;
    uint32_t v = *s++; len--;
    while ((n < 3) && (len > 0)) {
        v = v << 8;
        v = v | *s++;
        len--; n++;
    }
    return v;
}

#define V_A 'a'
#define V_BO  (('b' << 8) | 'o'))
#define V_ABBA ...
#define V_2 ...

int FindTag(char *s, size_t len)
{
    uint32_t v = GetUpToFour(s, len);
    if (v == V_A) return Tag_A;
    if (v == V_BO) return Tag_B;
    if (v == V_ABBA) {
        v = GetUpToFour(s, len);
        if (v == V_2) return Tag_Abba2;
     }
     return Tag_NotFound;
}

This code generator isn't smart enough to generate such code yet.
"""

import string
from html_entitites import html_entities

# first letter upper case, rest lower case
def capitalize(s):
    s = s.lower()
    return s[0].upper() + s[1:]

# This list has been generated by instrumenting MobiHtmlParse.cpp
# to dump all tags we see in a mobi file
g_tags_str = "a abbr acronym area audio b base basefont blockquote body br center code col dd div dl dt em font frame guide h1 h2 h3 h4 h5 head hr html i img input lh li link mbp:pagebreak meta object ol p pagebreak param pre reference s small span strike strong style sub sup table td th title tr tt u ul video"
g_attrs_str = "size href color filepos border valign rowspan colspan link vlink style face value bgcolor class id mediarecindex controls recindex title lang clear xmlns xmlns:dc width align height"
g_align_attr_str = "left right center justify"

################# Css colors

# array of name/value for css colors, value is what goes inside MKRGB()
# based on https://developer.mozilla.org/en/CSS/color_value
# TODO: add more colors
g_css_colors = [
    ["black",        "0, 0, 0"],
    ["white",        "255,255,255"],
    ["gray",         "128,128,128"],
    ["red",          "255,0,0"],
    ["green",        "0,128,0"],
    ["blue",         "0,0,255"],
    ["transparent",  "0,0,0,0"],
    ["yellow",       "255,255,0"],
    ];

css_colors_c = """
static const char *gCssKnownColorsStrings = "%s";
static ARGB gCssKnownColorsValues[] = { %s };

static bool GetKnownCssColor(const char *name, ARGB& colOut)
{
    int pos = str::FindStrPosI(gCssKnownColorsStrings, name, str::Len(name));
    if (-1 == pos)
        return false;
    colOut = gCssKnownColorsValues[pos];
    return true;
}
"""

def mrgb(col):
    els = col.split(",")
    if 3 == len(els): return "MKRGB(%s)" % col
    if 4 == len(els): return "MKARGB(%s)" % col
    assert 0

def gen_css_colors():
    g_css_colors.sort(key=lambda a: a[0])
    names = [v[0] for v in g_css_colors]
    vals = [mrgb(v[1]) for v in g_css_colors]
    names_c = string.join(names, "\\0") + "\\0"
    vals_c = string.join(vals, ", ")
    return css_colors_c % (names_c, vals_c)

################# Html entities

html_entities_c = """
// map of entity names to their Unicde runes, based on
// http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
// the order of strings in gHtmlEntityNames corresponds to
// order of Unicode runes in gHtmlEntityRunes
static const char *gHtmlEntityNames = "%s";

static uint16 gHtmlEntityRunes[] = { %s };

#define MAX_ENTITY_NAME_LEN %d
#define MAX_ENTITY_CHAR     %d

// returns -1 if didn't find
int HtmlEntityNameToRune(const char *name, size_t nameLen)
{
    if (nameLen > MAX_ENTITY_NAME_LEN)
        return -1;
    int pos = str::FindStrPos(gHtmlEntityNames, name, nameLen);
    if (-1 == pos)
        return -1;
    return (int)gHtmlEntityRunes[pos];
}
"""

def gen_html_entities():
    ent = html_entities
    # sort by entity names
    ent.sort(key=lambda a: a[0])
    names = [v[0] for v in ent]
    max_name_len = 0
    max_char = 0
    for n in names:
        if len(n) > max_name_len:
            max_name_len = len(n)
        for c in n:
            if ord(c) > max_char:
                max_char = ord(c)

    codes = [str(v[1]) for v in ent]
    names_c = string.join(names, "\\0") + "\\0"
    codes_c = string.join(codes, ", ")
    return html_entities_c % (names_c, codes_c, max_name_len, max_char)

################# Self-closing tags

self_closing_tmpl = """
static uint8 gSelfClosingTags[] = { %s };

STATIC_ASSERT(Tag_Last < 256, too_many_tags);

//prototypes:
//bool IsTagSelfClosing(const char *s, size_t len = -1);
//bool IsTagSelfClosing(HtmlTag tag);

static bool IsInArray(uint8 val, uint8 *arr, size_t arrLen)
{
    while (arrLen-- > 0) {
        if (val == *arr++)
            return true;
    }
    return false;
}

bool IsTagSelfClosing(HtmlTag tag)
{
    return IsInArray((uint8)tag, gSelfClosingTags, dimof(gSelfClosingTags));
}

bool IsTagSelfClosing(const char *s, size_t len)
{
    HtmlTag tag = FindTag(s, len);
    return IsTagSelfClosing(tag);
}
"""

g_self_closing_tags = [ "area", "base", "basefont", "br", "col", "frame", 
    "hr", "img", "input", "link", "meta", "param", "pagebreak", "mbp:pagebreak"];

def gen_self_closing_tags():
    names = g_self_closing_tags
    names.sort()
    # TODO: could sort enums by their values and optimize IsSelfClosingTag(HtmlTag tag) by
    # doing early exit if our value is > curr value in array
    enums = [enum_name_from_name(name, "Tag") for name in names]
    enums_c = string.join(enums, ", ")
    return self_closing_tmpl % (enums_c)

################# Inline tags

g_inline_tags = [ "a", "abbr", "acronym", "b", "br", "em", "font", "i", "img", "s", "small", "span",
    "strike", "strong", "sub", "sup", "u"]

inlline_tags_tmpl = """
static uint8 gInlineTags[] = { %s };

//prototypes:
//bool IsInlineTag(HtmlTag tag);

bool IsInlineTag(HtmlTag tag)
{
    return IsInArray((uint8)tag, gInlineTags, dimof(gInlineTags));
}
"""

def gen_inline_tags():
    names = g_inline_tags
    names.sort()
    # TODO: could sort enums by their values and optimize IsSelfClosingTag(HtmlTag tag) by
    # doing early exit if our value is > curr value in array
    enums = [enum_name_from_name(name, "Tag") for name in names]
    enums_c = string.join(enums, ", ")
    return inlline_tags_tmpl % (enums_c)

################# Html tags and attributes

html_tags_tmpl = """
// enums must match HTML_TAGS_STRINGS order
enum HtmlTag {
    %s
};

#define HTML_TAGS_STRINGS "%s"

//prototype:
//HtmlTag FindTag(const char *s, size_t len = -1);

HtmlTag FindTag(const char *s, size_t len)
{
    if (-1 == len)
        len = str::Len(s);
    return (HtmlTag)str::FindStrPosI(HTML_TAGS_STRINGS, s, len);
}
"""

html_attr_tmpl = """
// enums must match HTML_ATTRS_STRINGS order
enum HtmlAttr {
    %s
};

#define HTML_ATTRS_STRINGS "%s"

static HtmlAttr FindAttr(char *attr, size_t len)
{
    return (HtmlAttr)str::FindStrPosI(HTML_ATTRS_STRINGS, attr, len);
}
"""

html_align_attr_tmpl = """
// enums must match ALIGN_ATTRS_STRINGS order
enum AlignAttr {
    %s
};
#define ALIGN_ATTRS_STRINGS "%s"

static AlignAttr FindAlignAttr(char *attr, size_t len)
{
    return (AlignAttr)FindStrPosI(ALIGN_ATTRS_STRINGS, attr, len);
}
"""

# given e.g. "br", returns "Tag_Br"
def enum_name_from_name(name, prefix):
    name = name.replace("-", ":")
    parts = name.split(":")
    parts = [capitalize(p) for p in parts]
    parts = [prefix] + parts
    return string.join(parts, "_")

def gen_enum_str_list(strings, prefix):
    strings = [t.lower() for t in strings.split(" ")]
    strings.sort()
    strings_c = string.join(strings, "\\0") + "\\0"
    strings.append("last")
    # el[0] is tag, el[1] is 0-based position of the tag
    enums = [(enum_name_from_name(el[0], prefix), el[1]) for el in zip(strings, range(len(strings)))]
    enums = [(prefix + "_NotFound", -1)] + enums
    enum_strings = ["%s = %d" % t for t in enums]
    enums_string = string.join(enum_strings, ",\n    ")
    return (enums_string, strings_c)

def gen_tmpl(names, prefix, template):
    (enums, strings) = gen_enum_str_list(names, prefix)
    return template % (enums, strings)

def main():
    print(gen_css_colors())
    print(gen_html_entities())
    #print(gen_tmpl(g_attrs_str, "Attr", html_tags_tmpl))
    print(gen_tmpl(g_align_attr_str, "Align", html_tags_tmpl))
    print(gen_tmpl(g_tags_str, "Tag", html_tags_tmpl))
    print(gen_self_closing_tags())
    print(gen_inline_tags())

if __name__ == "__main__":
    main()
